package com.yanx.framework.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;

import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 缓存配置类
 *
 * @author gotanks
 */
@Configuration
@EnableCaching
@EnableConfigurationProperties(CacheProperties.class)
@ConditionalOnClass(RedisTemplate.class)
public class RedisCacheConfig extends CachingConfigurerSupport {
    @Autowired
    private CacheProperties cacheProperties;

    @Autowired
    private Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder;

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Bean
    @Primary
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        ObjectMapper objectMapper = jackson2ObjectMapperBuilder.createXmlMapper(false).build();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);

        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
        serializer.setObjectMapper(objectMapper);

//        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);

        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setDefaultSerializer(RedisSerializer.string());
        template.setValueSerializer(serializer);
        template.setHashValueSerializer(serializer);
        template.afterPropertiesSet();
        return template;
    }

    /**
     * 默认缓存管理器
     * <br>
     * 使用样例1：@Cacheable(cacheNames = "demoCache", key = "#id")// 5分钟
     * 使用样例2：@Cacheable(cacheNames = "userCache", key = "#id")// 默认10分钟
     * 使用样例3：@Cacheable(cacheNames = "customCache#60", key = "#id") // 自定义缓存60秒
     */
    @Override
    @Bean
    @Primary
    public RedisCacheManager cacheManager() {
        CacheProperties.Redis redisProperties = cacheProperties.getRedis();
        Duration timeToLive = redisProperties.getTimeToLive();
        if (timeToLive == null) {
            timeToLive = Duration.ofDays(1);
        }
        RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
                // 设置缓存管理器管理的缓存的默认过期时间
                .entryTtl(timeToLive)
                .disableCachingNullValues();
//                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
//                //使用json格式的来做存储数据类型，方便给各种语言方便使用
//                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
//        if (redisProperties.getTimeToLive() != null) {
//            defaultCacheConfig = defaultCacheConfig.entryTtl(redisProperties.getTimeToLive());
//        }
//        if (redisProperties.getKeyPrefix() != null) {
//            defaultCacheConfig = defaultCacheConfig.prefixKeysWith(redisProperties.getKeyPrefix());
//        }
//        if (!redisProperties.isCacheNullValues()) {
//            defaultCacheConfig = defaultCacheConfig.disableCachingNullValues();
//        }
//        if (!redisProperties.isUseKeyPrefix()) {
//            defaultCacheConfig = defaultCacheConfig.disableKeyPrefix();
//        }
        // 针对不同cacheName，设置不同的过期时间
        Map<String, RedisCacheConfiguration> initialCacheConfiguration = new LinkedHashMap<String, RedisCacheConfiguration>() {
            private static final long serialVersionUID = 1L;

            {
                this.put("demoCache", RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(5)));
                // this.put("userCache", RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(10))); // 10分钟
                // TODO 其他自定义缓存时长...
            }
        };
        return new CustomRedisCacheManager(
                RedisCacheWriter.nonLockingRedisCacheWriter(this.redisConnectionFactory),
                defaultCacheConfig,
                initialCacheConfiguration
        );
    }

    /**
     * 自定义Redis缓存管理器 - 支持缓存名#缓存秒数
     *
     * @author gotanks
     */
    class CustomRedisCacheManager extends RedisCacheManager {

        CustomRedisCacheManager(RedisCacheWriter cacheWriter,
                                RedisCacheConfiguration defaultCacheConfiguration,
                                Map<String, RedisCacheConfiguration> initialCacheConfigurations) {
            super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations, true);
        }

        @Override
        protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
            String[] array = name.split("#");
            name = array[0];
            if (array.length > 1) { // 解析TTL
                long ttlSecond = Long.parseLong(array[1]);
                cacheConfig = cacheConfig.entryTtl(Duration.ofSeconds(ttlSecond)); // 秒
            }
            return super.createRedisCache(name, cacheConfig);
        }
    }
}
